package vip.web3.api.interceptor;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import vip.web3.api.annotation.CryptoType;
import vip.web3.common.utils.DesUtil;
import vip.web3.common.utils.R;
import vip.web3.common.utils.SignUtils;
import vip.web3.common.utils.YmlUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * 请求参数解密过滤器<br>
 * 需要解密的方法，在对应方法或者类上添加注解@CryptoType(CryptoType.CRYPTO)<br>
 * ，“encrypt”： 加密后内容<br>
 * FIXME 当前只做请求参数加密，返回值未做加密
 */
@Component
@WebFilter(urlPatterns = {"/*"}, filterName = "requestDecryptFilter")
public class RequestDecryptFilter extends OncePerRequestFilter implements ApplicationContextAware {

    private static Logger logger = LoggerFactory.getLogger(RequestDecryptFilter.class);

    /**
     * 时间戳超时设置60s
     */
    private static final long TIMEOUT = 60 * 1000L;

    /**
     * 方法映射集
     */
    private static List<HandlerMapping> handlerMappings;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            ServletContext context = request.getServletContext();
            ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
            String apiUrl = request.getContextPath().concat("/api").replaceAll("//", "/");
            if (Objects.nonNull(request.getRequestURI()) && !request.getRequestURI().startsWith(apiUrl)) {
                filterChain.doFilter(request, response);
                return;
            }
            if (request.getRequestURI().contains(".html")
                    || request.getRequestURI().contains(".png")
                    || request.getRequestURI().contains("/apppic")
                    || request.getRequestURI().contains(".js")
                    || request.getRequestURI().contains(".apk")
                    || request.getRequestURI().contains("websocket")
                    || request.getRequestURI().contains(".css")) {
                filterChain.doFilter(request, response);
                return;
            }
            Object handler = getHandler(request).getHandler();
            if (handler instanceof HandlerMethod) {
                HandlerMethod method = (HandlerMethod) handler;
                // 方法上注解
                CryptoType type = method.getMethodAnnotation(CryptoType.class);
                if (null == type) {
                    // 类上是否有注解
                    type = method.getBeanType().getAnnotation(CryptoType.class);
                    if (null == type) {
                        filterChain.doFilter(request, response);
                        return;
                    }
                }
                if (type.value() != CryptoType.Type.CRYPTO) {
                    // 不是解密跳过
                    filterChain.doFilter(request, response);
                    return;
                }
            } else {
                filterChain.doFilter(request, response);
                return;
            }
        } catch (Exception e) {
            logger.error("", e);
            filterChain.doFilter(request, response);
            return;
        }
        try {
            //先验签
            logger.debug("Start verifySign ->");
            if (!verifySign(request)) {
                // 不通过
                logger.error("VerifySign fail");
                String requestURI = request.getRequestURI();
                StringBuffer requestURL = request.getRequestURL();
                logger.error("requestURI,{}", requestURI);
                logger.error("requestURL,{}", requestURL.toString());
                response.setContentType("application/json; charset=UTF-8");
                response.getWriter().write(JSON.toJSONString(R.error("系统异常,请稍后重试!")));
            } else {
                // 调用自定义request解析参数
                filterChain.doFilter(new DecryptRequest(request), response);
            }
        } catch (Exception e) {
            // 异常处理
            e.printStackTrace();
            logger.debug("Decrypt fail");
            response.setContentType("application/json; charset=UTF-8");
            response.getWriter().write(JSON.toJSONString(R.error("系统异常,请稍后重试!")));
        }
    }

    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 验证签名是否一致
     *
     * @param request
     */
    private boolean verifySign(HttpServletRequest request) throws Exception {
        String timestamp = request.getHeader("timestamp");
        String sign = request.getParameter("sign");
        logger.debug("VerifySign params -> timestamp:{}, sign:{}", timestamp, sign);
        if (StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(sign)) {
            logger.warn("VerifySign head param timestamp or sign is empty");
            return false;
        }
        // 时间戳是否过期 （60s）
        if (System.currentTimeMillis() - Long.parseLong(timestamp) > TIMEOUT) {
            // 超时
            logger.warn("VerifySign timestamp timeout");
            return false;
        }
        //从header中获取token
        String token = request.getHeader("token");
        //如果header中不存在token，则从参数中获取token
        if (org.apache.commons.lang.StringUtils.isBlank(token)) {
            token = request.getParameter("token");
            if (token == null) {
                token = "";
            }
        }
        String paramStr = "";
        String encrypt = request.getParameter("encrypt");
        String params = DesUtil.decryptDES(encrypt, YmlUtils.getFromYml("props.key").toString());
        paramStr = SignUtils.formatUrlMap(SignUtils.getUrlParams(params), false, false);

        return sign.equalsIgnoreCase(sign(token, timestamp, paramStr));
    }

    /**
     * 签名
     *
     * @param token
     * @param timestamp
     * @param params
     */
    private static String sign(String token, String timestamp, String params) {
        StringBuilder paramValues = new StringBuilder();
        paramValues.append("timestamp=" + timestamp);
        return SignUtils.md5Password(paramValues.toString());
    }

    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis());
        TreeMap<String, String> param = new TreeMap<>();
        param.put("token", "208630e544f64d5eb78558714035bf8d");
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        try {
            Thread.sleep(3000);

        } catch (Exception e) {

        }
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
                HandlerMapping.class, true, false);

        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);

        }
    }

    /**
     * 获取访问目标方法
     *
     * @param request
     * @return HandlerExecutionChain
     * @throws Exception
     */
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings == null) {
            logger.error("初始化的handlerMappings池为空--------------------------------------------------------------");
        }
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name ''");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

    /**
     * 解密request封装
     *
     * aut-horldy
     */
    private class DecryptRequest extends HttpServletRequestWrapper {

        private static final String APPLICATION_JSON = "application/json";
        /**
         * 所有参数的Map集合
         */
        private Map<String, String[]> parameterMap;
        /**
         * 输入流
         */
        private InputStream inputStream;

        public DecryptRequest(HttpServletRequest request) throws IOException {
            super(request);
            String contentType = request.getHeader("Content-Type");
            logger.debug("DecryptRequest -> contentType:{}", contentType);
            String encrypt = null;
            encrypt = request.getParameter("encrypt");
            logger.debug("DecryptRequest -> encrypt:{}", encrypt);
            // 解密
            String params = decrypt(encrypt);

            if (null != contentType && contentType.contains(APPLICATION_JSON)) {
                if (this.inputStream == null) {
                    this.inputStream = new DecryptInputStream(new ByteArrayInputStream(params.getBytes()));
                }
            }
            parameterMap = buildParams(params);
        }

        private String decrypt(String encrypt) throws IOException {
            try {
                // 解密
                return DesUtil.decryptDES(encrypt, YmlUtils.getFromYml("props.key").toString());
            } catch (Exception e) {
                logger.error("", e);
                throw new IOException(e.getMessage());
            }
        }

        private Map<String, String[]> buildParams(String src) throws UnsupportedEncodingException {
            Map<String, String[]> map = new HashMap<>();
            Map<String, String> params = SignUtils.getUrlParams(src);
            for (String key : params.keySet()) {
                map.put(key, new String[]{params.get(key)});
            }
            return map;
        }

        @Override
        public String getParameter(String name) {
            String[] values = getParameterMap().get(name);
            if (values != null) {
                return (values.length > 0 ? values[0] : null);
            }
            return super.getParameter(name);
        }

        @Override
        public String[] getParameterValues(String name) {
            String[] values = getParameterMap().get(name);
            if (values != null) {
                return values;
            }
            return super.getParameterValues(name);
        }

        @Override
        public Enumeration<String> getParameterNames() {
            Map<String, String[]> multipartParameters = getParameterMap();
            if (multipartParameters.isEmpty()) {
                return super.getParameterNames();
            }

            Set<String> paramNames = new LinkedHashSet<String>();
            Enumeration<String> paramEnum = super.getParameterNames();
            while (paramEnum.hasMoreElements()) {
                paramNames.add(paramEnum.nextElement());
            }
            paramNames.addAll(multipartParameters.keySet());
            return Collections.enumeration(paramNames);
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return null == parameterMap ? super.getParameterMap() : parameterMap;
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return this.inputStream == null ? super.getInputStream() : (ServletInputStream) this.inputStream;
        }
    }

    /**
     * 自定义ServletInputStream
     *
     * aut-horldy
     */
    private class DecryptInputStream extends ServletInputStream {

        private final InputStream sourceStream;

        /**
         * Create a DelegatingServletInputStream for the given source stream.
         *
         * @param sourceStream the source stream (never {@code null})
         */
        public DecryptInputStream(InputStream sourceStream) {
            Assert.notNull(sourceStream, "Source InputStream must not be null");
            this.sourceStream = sourceStream;
        }

        @Override
        public int read() throws IOException {
            return this.sourceStream.read();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.sourceStream.close();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }
}

